/**
 * 
 */
package cz.cvut.fit.crhonjar.mi.run.hawera.tree.expression;

import cz.cvut.fit.crhonjar.mi.run.hawera.exception.ErrorCode;
import cz.cvut.fit.crhonjar.mi.run.hawera.exception.HaweraException;
import cz.cvut.fit.crhonjar.mi.run.hawera.tree.TreeVisitor;

/**
 * @author Jarec
 * 
 */
public class BinaryTree extends ExpressionTree {

	private ExpressionTree leftSide;
	private ExpressionTree rightSide;
	private BinaryOperator operator;

	/**
	 * constructs binaryTree
	 * 
	 * @param leftSide
	 * @param rightSide
	 * @param operator
	 */
	public BinaryTree(ExpressionTree leftSide, ExpressionTree rightSide,
			BinaryOperator operator) {
		super();
		this.log.debug("> BinaryTree");
		if (leftSide == null) {
			this.log.error("LeftSide is null!");
			throw new NullPointerException(
					"Cannot create BinaryTree with null parameter!");
		}
		if (rightSide == null) {
			this.log.error("rightSide is null!");
			throw new NullPointerException(
					"Cannot create BinaryTree with null parameter!");
		}
		if (operator == null) {
			this.log.error("operator is null!");
			throw new NullPointerException(
					"Cannot create BinaryTree with null parameter!");
		}
		this.leftSide = leftSide;
		this.rightSide = rightSide;
		this.operator = operator;
		this.log.debug("< BinaryTree");
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * cz.cvut.fit.crhonjar.mi.run.hawera.tree.Tree#accept(cz.cvut.fit.crhonjar
	 * .mi.run.hawera.tree.TreeVisitor)
	 */
	@Override
	public void accept(TreeVisitor visitor) {
		visitor.visitBinary(this);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see cz.cvut.fit.crhonjar.mi.run.hawera.tree.expression.ExpressionTree#
	 * evaluableAtCompileTime()
	 */
	@Override
	public boolean evaluableAtCompileTime() throws HaweraException {
		this.log.debug("> evaluableAtCompileTime");
		if (this.getLeftSide().evaluableAtCompileTime()
				&& this.getRightSide().evaluableAtCompileTime()) {
			final ExpressionType leftSideType = this.getLeftSide().getType();
			final ExpressionType rightSideType = this.getRightSide().getType();
			final BinaryOperator oper = this.getOperator();
			if (((leftSideType == ExpressionType.FIXED) || (leftSideType == ExpressionType.FLOAT))
					&& ((rightSideType == ExpressionType.FIXED) || (rightSideType == ExpressionType.FLOAT))
					&& ((oper == BinaryOperator.DIV)
							|| (oper == BinaryOperator.EQ)
							|| (oper == BinaryOperator.GE)
							|| (oper == BinaryOperator.GT)
							|| (oper == BinaryOperator.LE)
							|| (oper == BinaryOperator.LT)
							|| (oper == BinaryOperator.MINUS)
							|| (oper == BinaryOperator.MUL)
							|| (oper == BinaryOperator.NEQ)
							|| (oper == BinaryOperator.PLUS) || (oper == BinaryOperator.POW))) {
				this.log.debug("< evaluableAtCompileTime result: true");
				return true;
			} else if ((leftSideType == ExpressionType.FIXED)
					&& (rightSideType == ExpressionType.FIXED)
					&& (oper == BinaryOperator.MOD)) {
				this.log.debug("< evaluableAtCompileTime result: true");
				return true;
			} else if ((leftSideType == ExpressionType.BOOLEAN)
					&& (rightSideType == ExpressionType.BOOLEAN)
					&& ((oper == BinaryOperator.AND)
							|| (oper == BinaryOperator.OR) || (oper == BinaryOperator.XOR))) {
				this.log.debug("< evaluableAtCompileTime result: true");
				return true;
			}
		}
		this.log.debug("< evaluableAtCompileTime result: false");
		return false;
	}

	/**
	 * @return the leftSide
	 */
	public ExpressionTree getLeftSide() {
		this.log.debug(">< getLeftSide");
		return this.leftSide;
	}

	/**
	 * @return the operator
	 */
	public BinaryOperator getOperator() {
		this.log.debug(">< getOperator");
		return this.operator;
	}

	/**
	 * @return the rightSide
	 */
	public ExpressionTree getRightSide() {
		this.log.debug(">< getRightSide");
		return this.rightSide;
	}

	/*
	 * trie to determine the type of expression
	 * 
	 * @see
	 * cz.cvut.fit.crhonjar.mi.run.hawera.tree.expression.ExpressionTree#getType
	 * ()
	 */
	@Override
	public ExpressionType getType() throws HaweraException {
		this.log.debug("> getType");
		switch (this.operator) {
		case OR:
		case XOR:
		case AND:
		case EQ:
		case GE:
		case GT:
		case LE:
		case LT:
		case NEQ:
		case INSTACEOF:
			this.log.debug("< getType: {}", ExpressionType.BOOLEAN);
			return ExpressionType.BOOLEAN;
		case MOD:
			this.log.debug("< getType: {}", ExpressionType.FIXED);
			return ExpressionType.FIXED;
		case PLUS:
		case MINUS:
		case DIV:
		case MUL:
		case POW:
			final ExpressionType leftSideType = this.leftSide.getType();
			final ExpressionType rightSideType = this.rightSide.getType();
			if (leftSideType == rightSideType) {
				this.log.debug("< getType: {}", leftSideType);
				return leftSideType;
			} else if (((leftSideType == ExpressionType.FLOAT) && (rightSideType == ExpressionType.FIXED))
					|| ((rightSideType == ExpressionType.FLOAT) && (leftSideType == ExpressionType.FIXED))) {
				this.log.debug("< getType: {}", ExpressionType.FLOAT);
				return ExpressionType.FLOAT;
			} else if ((rightSideType == ExpressionType.OBJECT)
					|| (leftSideType == ExpressionType.OBJECT)) {
				this.log.debug("< getType: {}", ExpressionType.OBJECT);
				return ExpressionType.OBJECT;
			} else {
				return leftSideType;
			}
		default:
			throw new HaweraException(ErrorCode.NON_SUPPORTED_BINARY_OPERATOR,
					"Operator " + this.operator + " is not supported!");
		}
	}

}
